package envelopes

import (
	"context"
	"database/sql"
	"errors"
	"gitee.com/wm1998/account/core/accounts"
	acservice "gitee.com/wm1998/account/services"
	"gitee.com/wm1998/infra/algo"
	"gitee.com/wm1998/infra/base"
	"gitee.com/wm1998/resk/services"
	"github.com/shopspring/decimal"
	log "github.com/sirupsen/logrus"
	"github.com/tietang/dbx"
)

// 抢/收红包业务逻辑
func (d *goodsDomain) Receive(ctx context.Context, dto services.RedEnvelopeReceiveDTO) (item *services.RedEnvelopeItemDTO, err error) {

	//1.创建收/抢红包的订单明细
	d.preCreateItem(dto)

	//2.查询出当前红包的剩余数量和剩余金额信息
	goods := d.Get(dto.EnvelopeNo)

	//3. 效验剩余红包和剩余金额：
	//	- 如果没有剩余，直接返回无可用红包金额
	if goods.RemainQuantity <= 0 || goods.RemainAmount.Cmp(decimal.NewFromFloat(0)) <= 0 {
		log.Errorf("%+v", goods) // 输出日志便于调试
		return nil, errors.New("没有足够的红包和金额了")
	}

	//4. - 如果有剩余，则【使用红包二倍均值算法，计算当前抢到的红包金额是多少，单位：元】
	nextAmount := d.nextAmount(goods)
	err = base.Tx(func(runner *dbx.TxRunner) error {
		dao := RedEnvelopeGoodsDao{runner: runner}
		//5. 使用乐观锁更新语句，更新剩余数量和剩余金额：
		rows, err := dao.UpdateBalance(goods.EnvelopeNo, nextAmount)

		// - 如果更新失败，也就是返回0，表示无可用红包数量和金额，抢红包失败
		if rows <= 0 || err != nil {
			log.Errorf("rows=%d, %s", rows, err.Error()) // 输出日志便于调试
			return errors.New("没有足够的红包和金额了")
		}
		// - 如果更新成功，也就是返回1，表示抢到红包
		// 6. 保存红包商品/订单详情-即为收/抢红包，返回插入新数据返回的主键id
		d.item.Quantity = 1
		d.item.PayStatus = int(services.Paying)
		d.item.AccountNo = dto.AccountNo
		d.item.RemainAmount = goods.RemainAmount.Sub(nextAmount) // 金额减法运算：红包剩余金额 - 抢到红包的金额
		d.item.Amount = nextAmount
		desc := goods.Username.String + "抢到了" + services.EnvelopeTypes[services.EnvelopeType(goods.EnvelopeType)]
		d.item.Desc = desc

		txCtx := base.WithValueContext(ctx, runner)
		_, err = d.item.Save(txCtx)
		//insert into `red_envelope_item`(`item_no`,`envelope_no`,`amount`,`quantity`,`remain_amount`,`account_no`,`recv_username`,`recv_user_id`,`pay_status`)
		// values(?,?,?,?,?,?,?,?,?)
		// 1LlS0fIHk4UxdnqC8wNGww5V7cJ
		//1LlU0eDA6WNt16ZbLKp3RwpFWcp
		if err != nil {
			log.Error(err)
			return err
		}

		// 7. 将收/抢到的红包金额从系统红包中间账户转入当前用户的资金账户
		status, err := d.transfer(txCtx, dto)
		if status == acservice.TransferedStatusSuccess {
			return nil
		}
		return err
	})
	return d.item.ToDTO(), err
}

// 转账：将抢到的红包金额从【系统红包中间账户】转入【当前用户】的资金账户
func (d *goodsDomain) transfer(ctx context.Context, dto services.RedEnvelopeReceiveDTO) (status acservice.TransferedStatus, err error) {
	systemAccount := base.GetSystemAccount() // 系统红包中间账户
	body := acservice.TradeParticipator{     // 构建交易主体
		AccountNo: systemAccount.AccountNo,
		UserId:    systemAccount.UserId,
		Username:  systemAccount.Username,
	}
	target := acservice.TradeParticipator{ // 构建交易目标
		AccountNo: dto.AccountNo,
		UserId:    dto.RecvUserId,
		Username:  dto.RecvUsername,
	}

	adomain := accounts.NewAccountDomain()

	//从系统红包资金账户扣减
	transfer := acservice.AccountTransferDTO{ // 构建转账接口的DTO
		TradeBody:   body,
		TradeTarget: target,
		TradeNo:     dto.EnvelopeNo,
		Amount:      d.item.Amount, // 抢到的红包金额
		ChangeType:  acservice.EnvelopeOutgoing,
		ChangeFlag:  acservice.FlagTransferOut,
		Decs:        "红包扣减：" + dto.EnvelopeNo,
	}
	status, err = adomain.TransferWithContextTx(ctx, transfer)
	if err != nil || status != acservice.TransferedStatusSuccess {
		return status, err
	}

	//从系统红包资金账户转入当前用户
	transfer = acservice.AccountTransferDTO{
		TradeBody:   target,
		TradeTarget: body,
		TradeNo:     dto.EnvelopeNo,
		Amount:      d.item.Amount,
		ChangeType:  acservice.EnvelopeIncoming,
		ChangeFlag:  acservice.FlagTransferIn,
		Decs:        "红包收入：" + dto.EnvelopeNo,
	}

	return adomain.TransferWithContextTx(ctx, transfer)
}

//预创建收/抢红包订单明细
func (d *goodsDomain) preCreateItem(dto services.RedEnvelopeReceiveDTO) {
	d.item.AccountNo = dto.AccountNo
	d.item.EnvelopeNo = dto.EnvelopeNo
	d.item.RecvUsername = sql.NullString{String: dto.RecvUsername, Valid: true}
	d.item.RecvUserId = dto.RecvUserId
	d.item.createItemNo()
}

var multiple = decimal.NewFromFloat(100.0) // 将元转换成分

// 使用红包二倍均值算法，计算当前抢到的红包金额是多少，单位：元
func (d *goodsDomain) nextAmount(goods *RedEnvelopeGoods) (amount decimal.Decimal) {
	if goods.RemainQuantity == 1 {
		return goods.RemainAmount
	}
	if goods.EnvelopeType == services.GeneralEnvelopeType { // 如果抢红包是普通红包
		return goods.AmountOne
	} else if goods.EnvelopeType == services.LuckyEnvelopeType { // 如果抢红包是碰运气红包
		// 将红包剩余金额转换为分后再通过IntPart()转换成整数
		cent := goods.RemainAmount.Mul(multiple).IntPart()
		// 【根据系统红包的剩余数量、剩余金额，使用二倍均值算法：二倍的最大可用金额平均值（防止出现0值），计算出抢到红包的红包金额】
		next := algo.DoubleAverage(int64(goods.RemainQuantity), cent)
		// 将抢到的红包金额由分转换为元
		amount = decimal.NewFromFloat(float64(next)).Div(multiple)
	} else {
		log.Error("不支持的红包类型")
	}
	return amount
}
